package org.kingdom.module.shiro;

import cn.hutool.core.bean.BeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.kingdom.module.account.entity.Account;
import org.kingdom.module.account.entity.Role;
import org.kingdom.module.account.service.IAccountService;
import org.kingdom.module.account.service.IPremissionService;
import org.kingdom.module.account.service.IRoleService;
import org.kingdom.module.shiro.jwt.JwtToken;
import org.kingdom.module.shiro.jwt.JwtUtil;
import org.kingdom.module.utils.CommonFlag;
import org.kingdom.module.utils.ConvertUtils;
import org.kingdom.module.utils.RedisUtil;
import org.kingdom.module.utils.SpringContextUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

/**
 * 用户登录鉴权和获取用户授权
 */
@Component
@Slf4j
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private IAccountService accountService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private IRoleService roleService;
    @Autowired
    private IPremissionService premissionService;

    /**
     * 必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
     * 触发检测用户权限时才会调用此方法，例如checkRole,checkPermission
     *
     * @param principals 身份信息
     * @return AuthorizationInfo 权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.info("===============Shiro权限认证开始============ [ roles、permissions]==========");
        String username = null;
        if (principals != null) {
            Account account = (Account) principals.getPrimaryPrincipal();
            username = account.getUsername();
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        Role role = roleService.getAccountRole(username);
        Set<String> roleSet = new HashSet<>();
        roleSet.add(role.getRoleCode());
        info.setRoles(roleSet);

        Set<String> premission = premissionService.listPremission(role.getId());
        info.addStringPermissions(premission);
        log.info("===============Shiro权限认证成功==============");
        return info;
    }

    /**
     * @param auth 用户登录的账号密码信息
     * @return 返回封装了用户信息的 AuthenticationInfo 实例
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        if (token == null) {
            log.info("————————身份认证失败——————————IP地址:  " + ConvertUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest()));
            throw new AuthenticationException("token为空!");
        }
        Account account = this.checkUserTokenIsEffect(token);
        return new SimpleAuthenticationInfo(account, token, getName());
    }

    /**
     * 校验token的有效性
     */
    public Account checkUserTokenIsEffect(String token) throws AuthenticationException {
        String username = JwtUtil.getUsername(token);
        if (username == null) {
            throw new AuthenticationException("token非法无效!");
        }
        log.debug("———校验token是否有效————checkUserTokenIsEffect——————— " + token);
        Account account = accountService.getAccountByUsername(username);
        if (BeanUtil.isEmpty(account)) {
            throw new AuthenticationException("用户不存在!");
        }
        if (account.getAccountStatus().equals(CommonFlag.FLAG_1)) {
            throw new AuthenticationException("账号已被冻结,请联系管理员!");
        }
        // 校验token是否超时失效 & 或者账号密码是否错误
        if (!jwtTokenRefresh(token, username, account.getUsernameCode())) {
            throw new AuthenticationException("Token失效，请重新登录!");
        }
        return account;
    }

    /**
     * JWTToken刷新生命周期 （实现： 用户在线操作不掉线功能）
     */
    public boolean jwtTokenRefresh(String token, String userName, String passWord) {
        String cacheToken = String.valueOf(redisUtil.get(CommonFlag.PREFIX_USER_TOKEN + token));
        if (ConvertUtils.isNotEmpty(cacheToken)) {
            // 校验token有效性
            if (!JwtUtil.verify(cacheToken, userName, passWord)) {
                String newAuthorization = JwtUtil.sign(userName, passWord);
                // 设置超时时间
                redisUtil.set(CommonFlag.PREFIX_USER_TOKEN + token, newAuthorization);
                redisUtil.expire(CommonFlag.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME * 2 / 1000);
                log.info("——————————用户在线操作，更新token保证不掉线—————————jwtTokenRefresh——————— " + token);
            }
            return true;
        }
        return false;
    }

    /**
     * 清除当前用户的权限认证缓存
     *
     * @param principals 权限信息
     */
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

}
